/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"

#ifndef WIN32

#ifdef HAVE_GTK

#include "gtk/gtk.h"
#include "gdk/gdkkeysyms.h"
#include <stdio.h>

/*#if _MSC_VER >= 1200
#pragma warning( disable: 4505 )
#pragma comment(lib,"gtk-win32-2.0.lib")
#pragma comment(lib,"glib-2.0.lib")
#pragma comment(lib,"gobject-2.0.lib")
#pragma comment(lib,"gdk-win32-2.0.lib")
#pragma comment(lib,"gdk_pixbuf-2.0.lib")
#endif*/


// TODO Fix the initial window size when flags=0.  Right now the initial window is by default
// 320x240 size.  A better default would be actual size of the image.  Problem
// is determining desired window size with trackbars while still allowing resizing.
//
// Gnome Totem source may be of use here, see bacon_video_widget_set_scale_ratio
// in totem/src/backend/bacon-video-widget-xine.c

////////////////////////////////////////////////////////////
// CvImageWidget GTK Widget Public API
////////////////////////////////////////////////////////////
typedef struct _CvImageWidget        CvImageWidget;
typedef struct _CvImageWidgetClass   CvImageWidgetClass;

struct _CvImageWidget {
    GtkWidget widget;
    CvMat* original_image;
    CvMat* scaled_image;
    int flags;
};

struct _CvImageWidgetClass {
    GtkWidgetClass parent_class;
};


/** Allocate new image viewer widget */
GtkWidget*     cvImageWidgetNew(int flags);

/** Set the image to display in the widget */
void           cvImageWidgetSetImage(CvImageWidget* widget, const CvArr* arr);

// standard GTK object macros
#define CV_IMAGE_WIDGET(obj)          GTK_CHECK_CAST (obj, cvImageWidget_get_type (), CvImageWidget)
#define CV_IMAGE_WIDGET_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, cvImageWidget_get_type (), CvImageWidgetClass)
#define CV_IS_IMAGE_WIDGET(obj)       GTK_CHECK_TYPE (obj, cvImageWidget_get_type ())

/////////////////////////////////////////////////////////////////////////////
// Private API ////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
GtkType        cvImageWidget_get_type(void);

static GtkWidgetClass* parent_class = NULL;

// flag to help size initial window
#define CV_WINDOW_NO_IMAGE 2

void cvImageWidgetSetImage(CvImageWidget* widget, const CvArr* arr) {
    CvMat* mat, stub;
    int origin = 0;

    //printf("cvImageWidgetSetImage\n");

    if (CV_IS_IMAGE_HDR(arr)) {
        origin = ((IplImage*)arr)->origin;
    }

    mat = cvGetMat(arr, &stub);

    if (widget->original_image && !CV_ARE_SIZES_EQ(mat, widget->original_image)) {
        cvReleaseMat(&widget->original_image);
    }
    if (!widget->original_image) {
        widget->original_image = cvCreateMat(mat->rows, mat->cols, CV_8UC3);
        gtk_widget_queue_resize(GTK_WIDGET(widget));
    }
    cvConvertImage(mat, widget->original_image,
                   (origin != 0 ? CV_CVTIMG_FLIP : 0) + CV_CVTIMG_SWAP_RB);
    if (widget->scaled_image) {
        cvResize(widget->original_image, widget->scaled_image, CV_INTER_AREA);
    }

    // window does not refresh without this
    gtk_widget_queue_draw(GTK_WIDGET(widget));
}

GtkWidget*
cvImageWidgetNew(int flags) {
    CvImageWidget* image_widget;

    image_widget = CV_IMAGE_WIDGET(gtk_type_new(cvImageWidget_get_type()));
    image_widget->original_image = 0;
    image_widget->scaled_image = 0;
    image_widget->flags = flags | CV_WINDOW_NO_IMAGE;

    return GTK_WIDGET(image_widget);
}

static void
cvImageWidget_realize(GtkWidget* widget) {
    CvImageWidget* image_widget;
    GdkWindowAttr attributes;
    gint attributes_mask;

    //printf("cvImageWidget_realize\n");
    g_return_if_fail(widget != NULL);
    g_return_if_fail(CV_IS_IMAGE_WIDGET(widget));

    GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
    image_widget = CV_IMAGE_WIDGET(widget);

    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.event_mask = gtk_widget_get_events(widget) |
                            GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
                            GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
    attributes.visual = gtk_widget_get_visual(widget);
    attributes.colormap = gtk_widget_get_colormap(widget);

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
    widget->window = gdk_window_new(widget->parent->window, &attributes, attributes_mask);

    widget->style = gtk_style_attach(widget->style, widget->window);

    gdk_window_set_user_data(widget->window, widget);

    gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE);
}

static CvSize cvImageWidget_calc_size(int im_width, int im_height, int max_width, int max_height) {
    float aspect = (float)im_width / (float)im_height;
    float max_aspect = (float)max_width / (float)max_height;
    if (aspect > max_aspect) {
        return cvSize(max_width, cvRound(max_width / aspect));
    }
    return cvSize(cvRound(max_height * aspect), max_height);
}

static void
cvImageWidget_size_request(GtkWidget*      widget,
                           GtkRequisition* requisition) {
    CvImageWidget* image_widget = CV_IMAGE_WIDGET(widget);

    //printf("cvImageWidget_size_request ");
    // the case the first time cvShowImage called or when AUTOSIZE
    if (image_widget->original_image &&
            ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
             (image_widget->flags & CV_WINDOW_NO_IMAGE))) {
        //printf("original ");
        requisition->width = image_widget->original_image->cols;
        requisition->height = image_widget->original_image->rows;
    }
    // default case
    else if (image_widget->scaled_image) {
        //printf("scaled ");
        requisition->width = image_widget->scaled_image->cols;
        requisition->height = image_widget->scaled_image->rows;
    }
    // the case before cvShowImage called
    else {
        //printf("default ");
        requisition->width = 320;
        requisition->height = 240;
    }
    //printf("%d %d\n",requisition->width, requisition->height);
}

static void cvImageWidget_set_size(GtkWidget* widget, int max_width, int max_height) {
    CvImageWidget* image_widget = CV_IMAGE_WIDGET(widget);

    //printf("cvImageWidget_set_size %d %d\n", max_width, max_height);

    // don't allow to set the size
    if (image_widget->flags & CV_WINDOW_AUTOSIZE) { return; }
    if (!image_widget->original_image) { return; }

    CvSize scaled_image_size = cvImageWidget_calc_size(image_widget->original_image->cols,
                               image_widget->original_image->rows, max_width, max_height);

    if (image_widget->scaled_image &&
            (image_widget->scaled_image->cols != scaled_image_size.width ||
             image_widget->scaled_image->rows != scaled_image_size.height)) {
        cvReleaseMat(&image_widget->scaled_image);
    }
    if (!image_widget->scaled_image) {
        image_widget->scaled_image = cvCreateMat(scaled_image_size.height, scaled_image_size.width,        CV_8UC3);


    }
    assert(image_widget->scaled_image);
}

static void
cvImageWidget_size_allocate(GtkWidget*     widget,
                            GtkAllocation* allocation) {
    CvImageWidget* image_widget;

    //printf("cvImageWidget_size_allocate\n");
    g_return_if_fail(widget != NULL);
    g_return_if_fail(CV_IS_IMAGE_WIDGET(widget));
    g_return_if_fail(allocation != NULL);

    widget->allocation = *allocation;
    image_widget = CV_IMAGE_WIDGET(widget);


    if ((image_widget->flags & CV_WINDOW_AUTOSIZE) == 0 && image_widget->original_image) {
        // (re) allocated scaled image
        if (image_widget->flags & CV_WINDOW_NO_IMAGE) {
            cvImageWidget_set_size(widget, image_widget->original_image->cols,
                                   image_widget->original_image->rows);
        } else {
            cvImageWidget_set_size(widget, allocation->width, allocation->height);
        }
        cvResize(image_widget->original_image, image_widget->scaled_image, CV_INTER_AREA);
    }

    if (GTK_WIDGET_REALIZED(widget)) {
        image_widget = CV_IMAGE_WIDGET(widget);

        if (image_widget->original_image &&
                ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
                 (image_widget->flags & CV_WINDOW_NO_IMAGE))) {
            widget->allocation.width = image_widget->original_image->cols;
            widget->allocation.height = image_widget->original_image->rows;
            gdk_window_move_resize(widget->window, allocation->x, allocation->y,
                                   image_widget->original_image->cols, image_widget->original_image->rows);
            if (image_widget->flags & CV_WINDOW_NO_IMAGE) {
                image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
                gtk_widget_queue_resize(GTK_WIDGET(widget));
            }
        } else {
            gdk_window_move_resize(widget->window,
                                   allocation->x, allocation->y,
                                   allocation->width, allocation->height);

        }
    }
}

static gboolean
cvImageWidget_expose(GtkWidget*      widget,
                     GdkEventExpose* event) {
    CvImageWidget* image_widget;

    g_return_val_if_fail(widget != NULL, FALSE);
    g_return_val_if_fail(CV_IS_IMAGE_WIDGET(widget), FALSE);
    g_return_val_if_fail(event != NULL, FALSE);

    if (event->count > 0) {
        return FALSE;
    }

    image_widget = CV_IMAGE_WIDGET(widget);

    gdk_window_clear_area(widget->window,
                          0, 0,
                          widget->allocation.width,
                          widget->allocation.height);
    if (image_widget->scaled_image) {
        // center image in available region
        int x0 = (widget->allocation.width - image_widget->scaled_image->cols) / 2;
        int y0 = (widget->allocation.height - image_widget->scaled_image->rows) / 2;

        gdk_draw_rgb_image(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL],
                           x0, y0, MIN(image_widget->scaled_image->cols, widget->allocation.width),
                           MIN(image_widget->scaled_image->rows, widget->allocation.height),
                           GDK_RGB_DITHER_MAX, image_widget->scaled_image->data.ptr, image_widget->scaled_image->step);
    } else if (image_widget->original_image) {
        gdk_draw_rgb_image(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL],
                           0, 0,
                           MIN(image_widget->original_image->cols, widget->allocation.width),
                           MIN(image_widget->original_image->rows, widget->allocation.height),
                           GDK_RGB_DITHER_MAX, image_widget->original_image->data.ptr, image_widget->original_image->step);
    }
    return TRUE;
}

static void
cvImageWidget_destroy(GtkObject* object) {
    CvImageWidget* image_widget;

    g_return_if_fail(object != NULL);
    g_return_if_fail(CV_IS_IMAGE_WIDGET(object));

    image_widget = CV_IMAGE_WIDGET(object);

    cvReleaseMat(&image_widget->scaled_image);
    cvReleaseMat(&image_widget->original_image);

    if (GTK_OBJECT_CLASS(parent_class)->destroy) {
        (* GTK_OBJECT_CLASS(parent_class)->destroy)(object);
    }
}

static void cvImageWidget_class_init(CvImageWidgetClass* klass) {
    GtkObjectClass* object_class;
    GtkWidgetClass* widget_class;

    object_class = (GtkObjectClass*) klass;
    widget_class = (GtkWidgetClass*) klass;

    parent_class = GTK_WIDGET_CLASS(gtk_type_class(gtk_widget_get_type()));

    object_class->destroy = cvImageWidget_destroy;

    widget_class->realize = cvImageWidget_realize;
    widget_class->expose_event = cvImageWidget_expose;
    widget_class->size_request = cvImageWidget_size_request;
    widget_class->size_allocate = cvImageWidget_size_allocate;
    widget_class->button_press_event = NULL;
    widget_class->button_release_event = NULL;
    widget_class->motion_notify_event = NULL;
}

static void
cvImageWidget_init(CvImageWidget* image_widget) {
    image_widget->original_image = 0;
    image_widget->scaled_image = 0;
    image_widget->flags = 0;
}

GtkType cvImageWidget_get_type(void) {
    static GtkType image_type = 0;

    if (!image_type) {
        static const GtkTypeInfo image_info = {
            (gchar*)"CvImageWidget",
            sizeof(CvImageWidget),
            sizeof(CvImageWidgetClass),
            (GtkClassInitFunc) cvImageWidget_class_init,
            (GtkObjectInitFunc) cvImageWidget_init,
            /* reserved_1 */ NULL,
            /* reserved_1 */ NULL,
            (GtkClassInitFunc) NULL
        };

        image_type = gtk_type_unique(GTK_TYPE_WIDGET, &image_info);
    }

    return image_type;
}
/////////////////////////////////////////////////////////////////////////////
// End CvImageWidget
/////////////////////////////////////////////////////////////////////////////


struct CvWindow;

typedef struct CvTrackbar {
    int signature;
    GtkWidget* widget;
    char* name;
    CvTrackbar* next;
    CvWindow* parent;
    int* data;
    int pos;
    int maxval;
    CvTrackbarCallback notify;
    CvTrackbarCallback2 notify2;
    void* userdata;
}
CvTrackbar;

typedef struct CvWindow {
    int signature;
    GtkWidget* widget;
    GtkWidget* frame;
    GtkWidget* paned;
    char* name;
    CvWindow* prev;
    CvWindow* next;

    int last_key;
    int flags;
    int status;//0 normal, 1 fullscreen (YV)

    CvMouseCallback on_mouse;
    void* on_mouse_param;

    struct {
        int pos;
        int rows;
        CvTrackbar* first;
    }
    toolbar;
}
CvWindow;


static gboolean icvOnClose(GtkWidget* widget, GdkEvent* event, gpointer user_data);
static gboolean icvOnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer user_data);
static void icvOnTrackbar(GtkWidget* widget, gpointer user_data);
static gboolean icvOnMouse(GtkWidget* widget, GdkEvent* event, gpointer user_data);

#ifdef HAVE_GTHREAD
int thread_started = 0;
static gpointer icvWindowThreadLoop();
GMutex*				   last_key_mutex;
GCond*				   cond_have_key;
GMutex*				   window_mutex;
GThread*			   window_thread;
GtkWidget*             cvTopLevelWidget = 0;
#endif

static int             last_key = -1;
static CvWindow* hg_windows = 0;

CV_IMPL int cvInitSystem(int argc, char** argv) {
    static int wasInitialized = 0;

    // check initialization status
    if (!wasInitialized) {
        hg_windows = 0;

        gtk_init(&argc, &argv);
        wasInitialized = 1;
    }

    return 0;
}

CV_IMPL int cvStartWindowThread() {
#ifdef HAVE_GTHREAD
    cvInitSystem(0, NULL);
    if (!thread_started) {
        if (!g_thread_supported()) {
            /* the GThread system wasn't inited, so init it */
            g_thread_init(NULL);
        }

        // this mutex protects the window resources
        window_mutex = g_mutex_new();

        // protects the 'last key pressed' variable
        last_key_mutex = g_mutex_new();

        // conditional that indicates a key has been pressed
        cond_have_key = g_cond_new();

        // this is the window update thread
        window_thread = g_thread_create((GThreadFunc) icvWindowThreadLoop,
                                        NULL, TRUE, NULL);
    }
    thread_started = window_thread != NULL;
    return thread_started;
#else
    return 0;
#endif
}

#ifdef HAVE_GTHREAD
gpointer icvWindowThreadLoop() {
    while (1) {
        g_mutex_lock(window_mutex);
        gtk_main_iteration_do(FALSE);
        g_mutex_unlock(window_mutex);

        // little sleep
        g_usleep(500);

        g_thread_yield();
    }
    return NULL;
}

#define CV_LOCK_MUTEX() \
if(thread_started && g_thread_self()!=window_thread){ g_mutex_lock( window_mutex ); } else { }

#define CV_UNLOCK_MUTEX() \
if(thread_started && g_thread_self()!=window_thread){ g_mutex_unlock( window_mutex); } else { }

#else
#define CV_LOCK_MUTEX()
#define CV_UNLOCK_MUTEX()
#endif

static CvWindow* icvFindWindowByName(const char* name) {
    CvWindow* window = hg_windows;
    while (window != 0 && strcmp(name, window->name) != 0) {
        window = window->next;
    }

    return window;
}

static CvWindow* icvWindowByWidget(GtkWidget* widget) {
    CvWindow* window = hg_windows;

    while (window != 0 && window->widget != widget &&
            window->frame != widget && window->paned != widget) {
        window = window->next;
    }

    return window;
}

double cvGetMode_GTK(const char* name) { //YV
    double result = -1;

    CV_FUNCNAME("cvGetMode_GTK");

    __BEGIN__;

    CvWindow* window;

    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name string");
    }

    window = icvFindWindowByName(name);
    if (!window) {
        CV_ERROR(CV_StsNullPtr, "NULL window");
    }

    CV_LOCK_MUTEX();
    result = window->status;
    CV_UNLOCK_MUTEX();

    __END__;
    return result;
}


void cvChangeMode_GTK(const char* name, double prop_value) {  //Yannick Verdie

    CV_FUNCNAME("cvChangeMode_GTK");

    __BEGIN__;

    CvWindow* window;

    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name string");
    }

    window = icvFindWindowByName(name);
    if (!window) {
        CV_ERROR(CV_StsNullPtr, "NULL window");
    }

    if (window->flags & CV_WINDOW_AUTOSIZE) { //if the flag CV_WINDOW_AUTOSIZE is set
        EXIT;
    }

    //so easy to do fullscreen here, Linux rocks !

    if (window->status == CV_WINDOW_FULLSCREEN && prop_value == CV_WINDOW_NORMAL) {
        CV_LOCK_MUTEX();
        gtk_window_unfullscreen(GTK_WINDOW(window->frame));
        window->status = CV_WINDOW_NORMAL;
        CV_UNLOCK_MUTEX();
        EXIT;
    }

    if (window->status == CV_WINDOW_NORMAL && prop_value == CV_WINDOW_FULLSCREEN) {
        CV_LOCK_MUTEX();
        gtk_window_fullscreen(GTK_WINDOW(window->frame));
        window->status = CV_WINDOW_FULLSCREEN;
        CV_UNLOCK_MUTEX();
        EXIT;
    }

    __END__;
}

CV_IMPL int cvNamedWindow(const char* name, int flags) {
    int result = 0;
    CV_FUNCNAME("cvNamedWindow");

    __BEGIN__;

    CvWindow* window;
    int len;

    cvInitSystem(1, (char**)&name);
    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name string");
    }

    // Check the name in the storage
    if (icvFindWindowByName(name) != 0) {
        result = 1;
        EXIT;
    }

    len = strlen(name);
    CV_CALL(window = (CvWindow*)cvAlloc(sizeof(CvWindow) + len + 1));
    memset(window, 0, sizeof(*window));
    window->name = (char*)(window + 1);
    memcpy(window->name, name, len + 1);
    window->flags = flags;
    window->signature = CV_WINDOW_MAGIC_VAL;
    window->last_key = 0;
    window->on_mouse = 0;
    window->on_mouse_param = 0;
    memset(&window->toolbar, 0, sizeof(window->toolbar));
    window->next = hg_windows;
    window->prev = 0;
    window->status = CV_WINDOW_NORMAL;//YV

    CV_LOCK_MUTEX();

    window->frame = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    window->paned = gtk_vbox_new(FALSE, 0);
    window->widget = cvImageWidgetNew(flags);
    gtk_box_pack_end(GTK_BOX(window->paned), window->widget, TRUE, TRUE, 0);
    gtk_widget_show(window->widget);
    gtk_container_add(GTK_CONTAINER(window->frame), window->paned);
    gtk_widget_show(window->paned);
    //
    // configure event handlers
    // TODO -- move this to CvImageWidget ?
    gtk_signal_connect(GTK_OBJECT(window->frame), "key-press-event",
                       GTK_SIGNAL_FUNC(icvOnKeyPress), window);
    gtk_signal_connect(GTK_OBJECT(window->widget), "button-press-event",
                       GTK_SIGNAL_FUNC(icvOnMouse), window);
    gtk_signal_connect(GTK_OBJECT(window->widget), "button-release-event",
                       GTK_SIGNAL_FUNC(icvOnMouse), window);
    gtk_signal_connect(GTK_OBJECT(window->widget), "motion-notify-event",
                       GTK_SIGNAL_FUNC(icvOnMouse), window);
    gtk_signal_connect(GTK_OBJECT(window->frame), "delete-event",
                       GTK_SIGNAL_FUNC(icvOnClose), window);

    gtk_widget_add_events(window->widget, GDK_EXPOSURE_MASK | GDK_BUTTON_RELEASE_MASK |
                          GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK) ;

    gtk_widget_show(window->frame);
    gtk_window_set_title(GTK_WINDOW(window->frame), name);

    if (hg_windows) {
        hg_windows->prev = window;
    }
    hg_windows = window;

    gtk_window_set_resizable(GTK_WINDOW(window->frame), (flags & CV_WINDOW_AUTOSIZE) == 0);


    // allow window to be resized
    if ((flags & CV_WINDOW_AUTOSIZE) == 0) {
        GdkGeometry geometry;
        geometry.min_width = 50;
        geometry.min_height = 50;
        gtk_window_set_geometry_hints(GTK_WINDOW(window->frame), GTK_WIDGET(window->widget),
                                      &geometry, (GdkWindowHints)(GDK_HINT_MIN_SIZE));
    }

    CV_UNLOCK_MUTEX();

    result = 1;
    __END__;

    return result;
}


static void icvDeleteWindow(CvWindow* window) {
    CvTrackbar* trackbar;

    if (window->prev) {
        window->prev->next = window->next;
    } else {
        hg_windows = window->next;
    }

    if (window->next) {
        window->next->prev = window->prev;
    }

    window->prev = window->next = 0;

    gtk_widget_destroy(window->frame);

    for (trackbar = window->toolbar.first; trackbar != 0;) {
        CvTrackbar* next = trackbar->next;
        cvFree(&trackbar);
        trackbar = next;
    }

    cvFree(&window);
#ifdef HAVE_GTHREAD
    // if last window, send key press signal
    // to jump out of any waiting cvWaitKey's
    if (hg_windows == 0 && thread_started) {
        g_cond_broadcast(cond_have_key);
    }
#endif
}


CV_IMPL void cvDestroyWindow(const char* name) {
    CV_FUNCNAME("cvDestroyWindow");

    __BEGIN__;

    CvWindow* window;

    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name string");
    }

    window = icvFindWindowByName(name);
    if (!window) {
        EXIT;
    }

    // note that it is possible for the update thread to run this function
    // if there is a call to cvShowImage in a mouse callback
    // (this would produce a deadlock on window_mutex)
    CV_LOCK_MUTEX();

    icvDeleteWindow(window);

    CV_UNLOCK_MUTEX();

    __END__;
}


CV_IMPL void
cvDestroyAllWindows(void) {
    CV_LOCK_MUTEX();

    while (hg_windows) {
        CvWindow* window = hg_windows;
        icvDeleteWindow(window);
    }
    CV_UNLOCK_MUTEX();
}

CvSize icvCalcOptimalWindowSize(CvWindow* window, CvSize new_image_size) {
    CvSize window_size;
    GtkWidget* toplevel = gtk_widget_get_toplevel(window->frame);
    gdk_drawable_get_size(GDK_DRAWABLE(toplevel->window),
                          &window_size.width, &window_size.height);

    window_size.width = window_size.width + new_image_size.width - window->widget->allocation.width;
    window_size.height = window_size.height + new_image_size.height - window->widget->allocation.height;

    return window_size;
}

CV_IMPL void
cvShowImage(const char* name, const CvArr* arr) {
    CV_FUNCNAME("cvShowImage");

    __BEGIN__;

    CvWindow* window;

    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name");
    }

    CV_LOCK_MUTEX();

    window = icvFindWindowByName(name);
    if (!window) {
        cvNamedWindow(name, 1);
        window = icvFindWindowByName(name);
    }
    if (window && arr) {
        CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget);
        cvImageWidgetSetImage(image_widget, arr);
    }

    CV_UNLOCK_MUTEX();

    __END__;
}

CV_IMPL void cvResizeWindow(const char* name, int width, int height) {
    CV_FUNCNAME("cvResizeWindow");

    __BEGIN__;

    CvWindow* window;
    CvImageWidget* image_widget;

    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name");
    }

    window = icvFindWindowByName(name);
    if (!window) {
        EXIT;
    }

    image_widget = CV_IMAGE_WIDGET(window->widget);
    if (image_widget->flags & CV_WINDOW_AUTOSIZE) {
        EXIT;
    }

    CV_LOCK_MUTEX();

    gtk_window_set_resizable(GTK_WINDOW(window->frame), 1);
    gtk_window_resize(GTK_WINDOW(window->frame), width, height);

    // disable initial resize since presumably user wants to keep
    // this window size
    image_widget->flags &= ~CV_WINDOW_NO_IMAGE;

    CV_UNLOCK_MUTEX();

    __END__;
}


CV_IMPL void cvMoveWindow(const char* name, int x, int y) {
    CV_FUNCNAME("cvMoveWindow");

    __BEGIN__;

    CvWindow* window;

    if (!name) {
        CV_ERROR(CV_StsNullPtr, "NULL name");
    }

    window = icvFindWindowByName(name);
    if (!window) {
        EXIT;
    }

    CV_LOCK_MUTEX();

    gtk_window_move(GTK_WINDOW(window->frame), x, y);

    CV_UNLOCK_MUTEX();

    __END__;
}


static CvTrackbar*
icvFindTrackbarByName(const CvWindow* window, const char* name) {
    CvTrackbar* trackbar = window->toolbar.first;

    for (; trackbar != 0 && strcmp(trackbar->name, name) != 0; trackbar = trackbar->next)
        { ; }

    return trackbar;
}

static int
icvCreateTrackbar(const char* trackbar_name, const char* window_name,
                  int* val, int count, CvTrackbarCallback on_notify,
                  CvTrackbarCallback2 on_notify2, void* userdata) {
    int result = 0;

    CV_FUNCNAME("icvCreateTrackbar");

    __BEGIN__;

    /*char slider_name[32];*/
    CvWindow* window = 0;
    CvTrackbar* trackbar = 0;

    if (!window_name || !trackbar_name) {
        CV_ERROR(CV_StsNullPtr, "NULL window or trackbar name");
    }

    if (count <= 0) {
        CV_ERROR(CV_StsOutOfRange, "Bad trackbar maximal value");
    }

    window = icvFindWindowByName(window_name);
    if (!window) {
        EXIT;
    }

    trackbar = icvFindTrackbarByName(window, trackbar_name);

    CV_LOCK_MUTEX();

    if (!trackbar) {
        int len = strlen(trackbar_name);
        trackbar = (CvTrackbar*)cvAlloc(sizeof(CvTrackbar) + len + 1);
        memset(trackbar, 0, sizeof(*trackbar));
        trackbar->signature = CV_TRACKBAR_MAGIC_VAL;
        trackbar->name = (char*)(trackbar + 1);
        memcpy(trackbar->name, trackbar_name, len + 1);
        trackbar->parent = window;
        trackbar->next = window->toolbar.first;
        window->toolbar.first = trackbar;

        GtkWidget* hscale_box = gtk_hbox_new(FALSE, 10);
        GtkWidget* hscale_label = gtk_label_new(trackbar_name);
        GtkWidget* hscale = gtk_hscale_new_with_range(0, count, 1);
        gtk_range_set_update_policy(GTK_RANGE(hscale), GTK_UPDATE_CONTINUOUS);
        gtk_scale_set_digits(GTK_SCALE(hscale), 0);
        //gtk_scale_set_value_pos( hscale, GTK_POS_TOP );
        gtk_scale_set_draw_value(GTK_SCALE(hscale), TRUE);

        trackbar->widget = hscale;
        gtk_box_pack_start(GTK_BOX(hscale_box), hscale_label, FALSE, FALSE, 5);
        gtk_widget_show(hscale_label);
        gtk_box_pack_start(GTK_BOX(hscale_box), hscale, TRUE, TRUE, 5);
        gtk_widget_show(hscale);
        gtk_box_pack_start(GTK_BOX(window->paned), hscale_box, FALSE, FALSE, 5);
        gtk_widget_show(hscale_box);

    }

    if (val) {
        int value = *val;
        if (value < 0) {
            value = 0;
        }
        if (value > count) {
            value = count;
        }
        gtk_range_set_value(GTK_RANGE(trackbar->widget), value);
        trackbar->pos = value;
        trackbar->data = val;
    }

    trackbar->maxval = count;
    trackbar->notify = on_notify;
    trackbar->notify2 = on_notify2;
    trackbar->userdata = userdata;
    gtk_signal_connect(GTK_OBJECT(trackbar->widget), "value-changed",
                       GTK_SIGNAL_FUNC(icvOnTrackbar), trackbar);

    // queue a widget resize to trigger a window resize to
    // compensate for the addition of trackbars
    gtk_widget_queue_resize(GTK_WIDGET(window->widget));


    CV_UNLOCK_MUTEX();

    result = 1;

    __END__;

    return result;
}


CV_IMPL int
cvCreateTrackbar(const char* trackbar_name, const char* window_name,
                 int* val, int count, CvTrackbarCallback on_notify) {
    return icvCreateTrackbar(trackbar_name, window_name, val, count,
                             on_notify, 0, 0);
}


CV_IMPL int
cvCreateTrackbar2(const char* trackbar_name, const char* window_name,
                  int* val, int count, CvTrackbarCallback2 on_notify2,
                  void* userdata) {
    return icvCreateTrackbar(trackbar_name, window_name, val, count,
                             0, on_notify2, userdata);
}


CV_IMPL void
cvSetMouseCallback(const char* window_name, CvMouseCallback on_mouse, void* param) {
    CV_FUNCNAME("cvSetMouseCallback");

    __BEGIN__;

    CvWindow* window = 0;

    if (!window_name) {
        CV_ERROR(CV_StsNullPtr, "NULL window name");
    }

    window = icvFindWindowByName(window_name);
    if (!window) {
        EXIT;
    }

    window->on_mouse = on_mouse;
    window->on_mouse_param = param;

    __END__;
}


CV_IMPL int cvGetTrackbarPos(const char* trackbar_name, const char* window_name) {
    int pos = -1;

    CV_FUNCNAME("cvGetTrackbarPos");

    __BEGIN__;

    CvWindow* window;
    CvTrackbar* trackbar = 0;

    if (trackbar_name == 0 || window_name == 0) {
        CV_ERROR(CV_StsNullPtr, "NULL trackbar or window name");
    }

    window = icvFindWindowByName(window_name);
    if (window) {
        trackbar = icvFindTrackbarByName(window, trackbar_name);
    }

    if (trackbar) {
        pos = trackbar->pos;
    }

    __END__;

    return pos;
}


CV_IMPL void cvSetTrackbarPos(const char* trackbar_name, const char* window_name, int pos) {
    CV_FUNCNAME("cvSetTrackbarPos");

    __BEGIN__;

    CvWindow* window;
    CvTrackbar* trackbar = 0;

    if (trackbar_name == 0 || window_name == 0) {
        CV_ERROR(CV_StsNullPtr, "NULL trackbar or window name");
    }

    window = icvFindWindowByName(window_name);
    if (window) {
        trackbar = icvFindTrackbarByName(window, trackbar_name);
    }

    if (trackbar) {
        if (pos < 0) {
            pos = 0;
        }

        if (pos > trackbar->maxval) {
            pos = trackbar->maxval;
        }
    }

    CV_LOCK_MUTEX();

    gtk_range_set_value(GTK_RANGE(trackbar->widget), pos);

    CV_UNLOCK_MUTEX();

    __END__;
}


CV_IMPL void* cvGetWindowHandle(const char* window_name) {
    void* widget = 0;

    CV_FUNCNAME("cvGetWindowHandle");

    __BEGIN__;

    CvWindow* window;

    if (window_name == 0) {
        CV_ERROR(CV_StsNullPtr, "NULL window name");
    }

    window = icvFindWindowByName(window_name);
    if (window) {
        widget = (void*)window->widget;
    }

    __END__;

    return widget;
}


CV_IMPL const char* cvGetWindowName(void* window_handle) {
    const char* window_name = "";

    CV_FUNCNAME("cvGetWindowName");

    __BEGIN__;

    CvWindow* window;

    if (window_handle == 0) {
        CV_ERROR(CV_StsNullPtr, "NULL window");
    }

    window = icvWindowByWidget((GtkWidget*)window_handle);
    if (window) {
        window_name = window->name;
    }

    __END__;

    return window_name;
}

static gboolean icvOnKeyPress(GtkWidget* /*widget*/,
                              GdkEventKey* event, gpointer /*user_data*/) {
    int code = 0;

    switch (event->keyval) {
    case GDK_Escape:
        code = 27;
        break;
    case GDK_Return:
    case GDK_Linefeed:
        code = '\n';
        break;
    case GDK_Tab:
        code = '\t';
        break;
    default:
        code = event->keyval;
    }

    code |= event->state << 16;

#ifdef HAVE_GTHREAD
    if (thread_started) { g_mutex_lock(last_key_mutex); }
#endif

    last_key = code;

#ifdef HAVE_GTHREAD
    if (thread_started) {
        // signal any waiting threads
        g_cond_broadcast(cond_have_key);
        g_mutex_unlock(last_key_mutex);
    }
#endif

    return FALSE;
}


static void icvOnTrackbar(GtkWidget* widget, gpointer user_data) {
    int pos = cvRound(gtk_range_get_value(GTK_RANGE(widget)));
    CvTrackbar* trackbar = (CvTrackbar*)user_data;

    if (trackbar && trackbar->signature == CV_TRACKBAR_MAGIC_VAL &&
            trackbar->widget == widget) {
        trackbar->pos = pos;
        if (trackbar->data) {
            *trackbar->data = pos;
        }
        if (trackbar->notify2) {
            trackbar->notify2(pos, trackbar->userdata);
        } else if (trackbar->notify) {
            trackbar->notify(pos);
        }
    }
}

static gboolean icvOnClose(GtkWidget* widget, GdkEvent* /*event*/, gpointer user_data) {
    CvWindow* window = (CvWindow*)user_data;
    if (window->signature == CV_WINDOW_MAGIC_VAL &&
            window->frame == widget) {
        icvDeleteWindow(window);
    }
    return TRUE;
}


static gboolean icvOnMouse(GtkWidget* widget, GdkEvent* event, gpointer user_data) {
    // TODO move this logic to CvImageWidget
    CvWindow* window = (CvWindow*)user_data;
    CvPoint2D32f pt32f = { -1., -1.};
    CvPoint pt = { -1, -1};
    int cv_event = -1, state = 0;
    CvImageWidget* image_widget = CV_IMAGE_WIDGET(widget);

    if (window->signature != CV_WINDOW_MAGIC_VAL ||
            window->widget != widget || !window->widget ||
            !window->on_mouse || !image_widget->original_image) {
        return FALSE;
    }

    if (event->type == GDK_MOTION_NOTIFY) {
        GdkEventMotion* event_motion = (GdkEventMotion*)event;

        cv_event = CV_EVENT_MOUSEMOVE;
        pt32f.x = cvRound(event_motion->x);
        pt32f.y = cvRound(event_motion->y);
        state = event_motion->state;
    } else if (event->type == GDK_BUTTON_PRESS ||
               event->type == GDK_BUTTON_RELEASE ||
               event->type == GDK_2BUTTON_PRESS) {
        GdkEventButton* event_button = (GdkEventButton*)event;
        pt32f.x = cvRound(event_button->x);
        pt32f.y = cvRound(event_button->y);


        if (event_button->type == GDK_BUTTON_PRESS) {
            cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDOWN :
                       event_button->button == 2 ? CV_EVENT_MBUTTONDOWN :
                       event_button->button == 3 ? CV_EVENT_RBUTTONDOWN : 0;
        } else if (event_button->type == GDK_BUTTON_RELEASE) {
            cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONUP :
                       event_button->button == 2 ? CV_EVENT_MBUTTONUP :
                       event_button->button == 3 ? CV_EVENT_RBUTTONUP : 0;
        } else if (event_button->type == GDK_2BUTTON_PRESS) {
            cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDBLCLK :
                       event_button->button == 2 ? CV_EVENT_MBUTTONDBLCLK :
                       event_button->button == 3 ? CV_EVENT_RBUTTONDBLCLK : 0;
        }
        state = event_button->state;
    }

    if (cv_event >= 0) {
        // scale point if image is scaled
        if ((image_widget->flags & CV_WINDOW_AUTOSIZE) == 0 &&
                image_widget->original_image &&
                image_widget->scaled_image) {
            // image origin is not necessarily at (0,0)
            int x0 = (widget->allocation.width - image_widget->scaled_image->cols) / 2;
            int y0 = (widget->allocation.height - image_widget->scaled_image->rows) / 2;
            pt.x = cvRound(((pt32f.x - x0) * image_widget->original_image->cols) /
                           image_widget->scaled_image->cols);
            pt.y = cvRound(((pt32f.y - y0) * image_widget->original_image->rows) /
                           image_widget->scaled_image->rows);
        } else {
            pt = cvPointFrom32f(pt32f);
        }

        if ((unsigned)pt.x < (unsigned)(image_widget->original_image->width) &&
                (unsigned)pt.y < (unsigned)(image_widget->original_image->height)) {
            int flags = (state & GDK_SHIFT_MASK ? CV_EVENT_FLAG_SHIFTKEY : 0) |
                        (state & GDK_CONTROL_MASK ? CV_EVENT_FLAG_CTRLKEY : 0) |
                        (state & (GDK_MOD1_MASK | GDK_MOD2_MASK) ? CV_EVENT_FLAG_ALTKEY : 0) |
                        (state & GDK_BUTTON1_MASK ? CV_EVENT_FLAG_LBUTTON : 0) |
                        (state & GDK_BUTTON2_MASK ? CV_EVENT_FLAG_MBUTTON : 0) |
                        (state & GDK_BUTTON3_MASK ? CV_EVENT_FLAG_RBUTTON : 0);
            window->on_mouse(cv_event, pt.x, pt.y, flags, window->on_mouse_param);
        }
    }

    return FALSE;
}


static gboolean icvAlarm(gpointer user_data) {
    *(int*)user_data = 1;
    return FALSE;
}


CV_IMPL int cvWaitKey(int delay) {
#ifdef HAVE_GTHREAD
    if (thread_started && g_thread_self() != window_thread) {
        gboolean expired;
        int my_last_key;

        // wait for signal or timeout if delay > 0
        if (delay > 0) {
            GTimeVal timer;
            g_get_current_time(&timer);
            g_time_val_add(&timer, delay * 1000);
            expired = !g_cond_timed_wait(cond_have_key, last_key_mutex, &timer);
        } else {
            g_cond_wait(cond_have_key, last_key_mutex);
            expired = false;
        }
        my_last_key = last_key;
        g_mutex_unlock(last_key_mutex);
        if (expired || hg_windows == 0) {
            return -1;
        }
        return my_last_key;
    } else {
#endif
        int expired = 0;
        guint timer = 0;
        if (delay > 0) {
            timer = g_timeout_add(delay, icvAlarm, &expired);
        }
        last_key = -1;
        while (gtk_main_iteration_do(TRUE) && last_key < 0 && !expired && hg_windows != 0)
            { ; }

        if (delay > 0 && !expired) {
            g_source_remove(timer);
        }
#ifdef HAVE_GTHREAD
    }
#endif
    return last_key;
}


#endif  // HAVE_GTK
#endif  // WIN32

/* End of file. */
